#include<iostream>
#include<string>
#include<cstdlib>  
#include<ctime>   
#include<cmath>
#include<vector>
#include<iomanip>
#include<thread>
#include<future>
#include<utility>
#include<random>
#include"readfile.cpp"
#include"ThreadPool.h"
using namespace std;
 
//  ---------------------------------------代码中可优化的地方暂且用 >>> 标注
//  --------------------------------------！！！！！！对于被回退的事件，原来的代码(即第一版代码pj2_0.cpp)没有考虑delete释放 >>>
int max_num_thread = 32;    //获取最大线程数(这个参数代表同时可以开启的最大线程数)

double unreverse_probability(double dT, double Vth){  //计算0.1秒不翻转的概率
    int x = 0.1 / dT;
    double a = Vth > 0.5? Vth:(1-Vth);
    return pow(a,x);
}
void get_num_thread(double dT, double Vth,double Tsim){  //更新最大线程max_num_thread
    if(compare_double(dT,0.05) == 0){
        if(Vth < 0.85 && Vth > 0.15){
            if(Tsim / dT < 500)
                max_num_thread = 64; 
            else if(Tsim / dT < 2500)
                max_num_thread = 32; 
            else 
                max_num_thread = 256; 
        }
        else if (Vth < 0.95 && Vth > 0.05){
            if(Tsim / dT < 500)
                max_num_thread = 64; 
            else if(Tsim / dT < 2500)
                max_num_thread = 32; 
            else 
                max_num_thread = 128; 
        }
        else {
                max_num_thread = 64; 
        }
        return;
    }
    if(dT < 0.025 ){
        double p = unreverse_probability(dT,Vth);
        if(p < 0.05)
        max_num_thread = 256;
        if(p < 1e-3){
            max_num_thread = 1024;
        }
    }
}
double R_V;       //全局变量：A事件的电压值V的精度
double getRandom_V()    //生成指定精度R_V的[0,1]之间的随机数V作为事件A的电压
{
    // double randValue = static_cast<double>(rand()) / RAND_MAX; //生成[0,1]之间的随机数  -------->>>   原来的取随机数方式不使用于多线程
    // return round(randValue / R_V) * R_V; //调整到指定精度
    // 使用线程局部的随机数生成器，线程安全
    thread_local std::mt19937 gen(std::random_device{}());
    std::uniform_real_distribution<> dis(0.0, 1.0);

    double randValue = dis(gen);
    return round(randValue / R_V) * R_V;
}

double R_T;       //全局变量：D事件的dT的精度
double getRandom_dT()  //生成指定精度R_T的[0,1]之间的随机数，作为事件D的dT
{

    // 使用线程局部随机数引擎，确保多线程安全
    thread_local std::mt19937 gen(std::random_device{}());
    std::uniform_real_distribution<> dis(0.1, 0.3);  // 包括边界值，但后面会排除

    double randomValue;
    do {
        randomValue = dis(gen);
        randomValue = round(randomValue / R_T) * R_T;
    } while (fabs(randomValue - 0.1) < EPSILON || fabs(randomValue - 0.3) < EPSILON); // 排除边界

    return randomValue;
}

int generateRandomTD()     // 随机生成tD
{
    // 创建线程本地的随机数生成器
    thread_local std::mt19937 generator(std::random_device{}());
    std::uniform_int_distribution<int> distribution(1, 10); // [1, 10]
    return distribution(generator);
}

class Event 
{
public:
    double dT;
    double T;                    
    int bM;

    virtual void print_event(std::ostream& out = std::cout) = 0; //打印

};

class Aevent : public Event
{
public:
    double V;
    double Vth;

    Aevent(double v, double vth, double dt, double t, int bm)
    {
        V = v;
        Vth = vth;
        dT = dt;
        T = t;
        bM = bm;
    }
    void print_event(std::ostream& out = std::cout) override{
        out << "A(" << V << " " << Vth << " " << dT << " " << std::setw(2) << std::setfill(' ')<< bM << ")" << "\t";
    }

};

class Devent : public Event
{
public:
    int V;

    Devent(int v, double dt, double t, int bm)
    {
        V = v;
        dT = dt;
        T = t;
        bM = bm;
    }
    void print_event(std::ostream& out = std::cout) override{
        out << "D(" << V << " " << dT << " " << bM << ")" << "\t";
    }

};


//仿真器,分为A/D两种
class Simulator
{
public:
    virtual void Push_Sim(double newT, Event*& p) = 0;      //推进仿真,产生的对应事件存储于指针p中
    virtual void Syn(Event* p1, Event*& p2) = 0;    //被同步，指针p1为事件的产生者，p2存储产生的事件
};

class Simulator_A : public Simulator
{
private:
    double Vth;
    double dT;
    int tA;      //单位ms，是bM=0或bM=1的A所需要sleep的时间，范围是[1,10]，由输入文件制定
public:
    vector<Aevent*> list_A;           //存储A事件
    Simulator_A(double vth, double dt)
    {
        Vth = vth;
        dT = dt;
    }
    void set_tA(int ta)   // 临时的设置tA的函数
    {
        tA = ta;
    }

    void Push_Sim(double newT, Event*& p) override{    //推进仿真

        std::this_thread::sleep_for(std::chrono::milliseconds(tA));    //sleep tA 毫秒
        double newV = getRandom_V();
        p = new Aevent(newV, Vth, dT, newT, 0);   //产生并且返回新的A事件
    }
    void Syn(Event* p1, Event*& p2) override{    //被同步

        Devent* p_D = static_cast<Devent*>(p1);   //指针显式转换
        double newT = p_D->T;
        double newV = p_D->V;
        p2 = new Aevent(newV, Vth, dT, newT, -1);   //产生并返回被同步的A事件
    }
    void Sim_A_thread(vector<Aevent*>& list_A_tmp, double T_start, int num_task)   // 每个线程的推进A仿真的函数
    {       // list_A_tmp用于存储本线程推进仿真产生的A事件(由于最后可能会有回退，所以命名为_tmp) T_start为要产生的第一个事件的T   num_task为要推进的A事件数
        Event* tmp;
        for(int i = 0; i < num_task; i++)
        {    
            Push_Sim(T_start + dT*i, tmp);
            list_A_tmp.push_back(static_cast<Aevent*>(tmp));
        }
    }
};

class Simulator_D : public Simulator
{
private:
    int tD;   //单位ms，是bM=1的D所需要sleep的时间,每次随机范围在[1,10]的一个整数
public:
    vector<Devent*> list_D;         //存储D事件
    void Push_Sim(double newT, Event*& p) override{   //推进仿真（实际上对于这个函数来说double newT参数并无意义）
        tD = generateRandomTD(); // 产生范围为[1, 10]的int随机数
        std::this_thread::sleep_for(std::chrono::milliseconds(tD));    //sleep tD 毫秒
        double new_T = list_D[list_D.size()-1]->T + list_D[list_D.size()-1]->dT;    //由于第一个D事件一定是由被同步函数Syn()产生，产生第一个事件后才可能推进仿真，所以这里不需要对list_D.size()判0
        int newV = 1 - list_D[list_D.size()-1]->V;     //对前一个D事件的V进行翻转  
        double new_dT = getRandom_dT();      //在(0.1, 0.3)范围内随机取dT
        p = new Devent(newV, new_dT, new_T, 1);   //产生新的D事件
    }
    void Syn(Event* p1, Event*& p2) override{   //被同步

        Aevent* p_A = static_cast<Aevent*>(p1);   //指针显式转换
        double newT = p_A->T;
        double new_dT = getRandom_dT();      //在(0.1, 0.3)范围内随机取dT
        int newV = (list_D.size() > 0 ? list_D[list_D.size()-1]->V : 0) * static_cast<int>(p_A->V > p_A->Vth);
        p2 = new Devent(newV, new_dT, newT, -1);     //产生新的D事件
    }
    void Push_Sim_thread(Event*& p1, Event*& p2)   //推进两次仿真
    {
        Push_Sim(0.0, p1);
        list_D.push_back(static_cast<Devent*>(p1));
        Push_Sim(0.0, p2);
    }
};

//仿真底板
class Sim_plate
{
private:
    double dT;
    double Vth;
    double Tsim;
    Simulator_A* Sim_A;
    Simulator_D* Sim_D;
    void print_info(bool print_first)  //信息打印函数，由函数start_Sim调用，目的是当A事件或D事件达到一定数目时，能够打印出仿真信息并清除无用的A事件和D事件（实际上只留下最新一个A事件和D事件，以便进行后续仿真），以实现一定程度上的“边仿真边打印”
    {       //根据A事件和D事件的特点，我们可以得到一个结论：在任意的一个仿真时间节点，一定有一个A事件，可能有一个D事件，也就是说A可以单独出现，D不能。所以我们有参数print_first  
            //bool print_first参数表示是否打印第一个A事件和第一个D事件，即当start_Sim第一次调用print_info函数时，传入参数print_first = true, 之后调用都令这个参数为false
            //这个操作的目的是防止每次留下的那个A和D事件重复打印
        double T;
        int decimal_places = -log10(R_T);  //计算输出保留的小数位数
        int count_A = print_first == true ? 0 : 1;
        int count_D = print_first == true ? 0 : 1;
        int end_A = Sim_A->list_A.size() - 1; 
        int end_D = Sim_D->list_D.size() - 1; 

        while(count_A <= end_A)       //打印
        {
            T = Sim_A->list_A[count_A]->T;
            cout << fixed << setprecision(decimal_places) << T << "\t";
            Sim_A->list_A[count_A]->print_event();
            count_A++;          
            if((count_D <= end_D) && (compare_double(T,Sim_D->list_D[count_D]->T) == 0))
            {
                Sim_D->list_D[count_D]->print_event();
                count_D++;
            }
            cout << "\n";
        }

        if(Sim_A->list_A.size() != 0)
        {
            for(int i = 0; i < (static_cast<int>(Sim_A->list_A.size()) - 1); i++)   //除了最新的一个A事件都清除
                delete Sim_A->list_A[i];
            Aevent* tmp_A = Sim_A->list_A[Sim_A->list_A.size() - 1];
            Sim_A->list_A.clear();
            Sim_A->list_A.push_back(tmp_A);
        }

        if(Sim_D->list_D.size() != 0)
        {
            for(int i = 0; i < (static_cast<int>(Sim_D->list_D.size()) - 1); i++)   //除了最新的一个D事件都清除
                delete Sim_D->list_D[i];
            Devent* tmp_D = Sim_D->list_D[Sim_D->list_D.size() - 1];
            Sim_D->list_D.clear();
            Sim_D->list_D.push_back(tmp_D);
        }
    }
    void print_end()   //打印结束信息
    {   
        if(compare_double(Sim_A->list_A.back()->T, Tsim) == 0)   //如果最后一个A事件发生的时间恰好和Tsim相等
            cout << "FINISH";      //另起一行打印FINISH（“另起一行”在print_info中已经完成）
        else   
        {
            int decimal_places = -log10(R_T);  //计算输出保留的小数位数
            cout << fixed << setprecision(decimal_places) << Tsim << "\tFINISH";   //否则另起一行打印Tsim和“FINISH”
        }
            
    }
public:
    Sim_plate(double dt, double vth, double tsim)
    {
        dT = dt;
        Vth = vth;
        Tsim = tsim;
        Sim_A = new Simulator_A(vth,dt);   //初始化A/D仿真器
        Sim_D = new Simulator_D();
    }

    void start_Sim()  // 另一种策略, 每次都并行推进max_num_task个A事件仿真，然后进行相关判断
    {
        bool loop = true;     //循环终止标志
        bool first = true;    //第一个循环标志，在第一个循环末尾就令first=false,之后first都为false
        int D_event_num = 0;   //记录每一次需要推进的D事件的个数, 值只能取0,1
        Event* last_D = NULL;  //最新的已经存储的D事件
        vector<vector<Aevent*>> A_thread(max_num_thread);  // 各个线程产生的A事件存储在每个vector数组中
        vector<Aevent*> temp_A_list;
        ThreadPool pool(max_num_thread);    //构造线程池对象
        while(loop)
        {
            int num_task;       // --------------这次并行推进仿真A事件的个数
            int max_left_task;   // 计算剩下的可能的最多的A事件
            if(Sim_A->list_A.size() == 0)
                max_left_task = static_cast<int>(Tsim/dT) + 1;
            else
            {
                double temp_d;
                if(D_event_num == 1)
                    temp_d = (Tsim - (last_D->T + last_D->dT))/dT;
                else
                    temp_d = (Tsim - Sim_A->list_A.back()->T)/dT;
                max_left_task = static_cast<int>(temp_d);

                if((temp_d - (static_cast<double>(max_left_task)+1.0)) > (-1.0e-6))   //对于误差进行处理
                    max_left_task += 1;
            }
            // 对于每个线程，我们只分配一个任务，所以num_task就等于线程数
            if(D_event_num == 0)
                num_task = (max_left_task < max_num_thread) ? max_left_task : max_num_thread;
            else   // 如果当前有至少需要一个D事件要推进仿真，那么要将D事件的推进仿真纳入并行中,所以A事件可用线程数要减一
                num_task = (max_left_task < (max_num_thread-1)) ? max_left_task : (max_num_thread-1);

            // 以下开启多个线程进行仿真-------------
            for (auto& vec : A_thread) vec.clear(); // 清空线程对应的数据

            double start_T = first ? 0 : ((D_event_num == 1) ? (last_D->T + last_D->dT + dT) : (Sim_A->list_A.back()->T + dT));  // 任务中第一个A事件的T
            
            // 开启线程
            future<void> d_future;
            if (D_event_num == 1) 
            {
                d_future = pool.enqueue([&]() {
                    Sim_D->Push_Sim(0.0, last_D);
                });
            } 

            vector<future<void>> futures;
            for (int i = 0; i < num_task; ++i) 
            {
                futures.emplace_back(
                    pool.enqueue([=, &A_thread]() {
                        Sim_A->Sim_A_thread(A_thread[i], start_T + dT * i, 1);
                    })
                );
            }

            for (auto& f : futures)
                f.get();  // 等待A事件仿真完成
            if (D_event_num > 0)
                d_future.get();  // 等待D事件仿真完成


            if(D_event_num == 1)    // 这种情况要对last_D进行存储且对A进行同步
            {
                Sim_D->list_D.push_back(static_cast<Devent*>(last_D));
                Event* tmp_A;
                Sim_A->Syn(last_D, tmp_A);     //A进行同步
                Sim_A->list_A.push_back(static_cast<Aevent*>(tmp_A));
            }

            // 主线程合并所有数据
            temp_A_list.clear();
            temp_A_list.reserve(num_task+1);
            if(Sim_A->list_A.size() != 0)
                temp_A_list.push_back(Sim_A->list_A.back());   // 先加入上个循环产生的最新的A事件(或这次被同步的A事件)
 
            for (int i = 0; i < num_task; ++i) 
                temp_A_list.push_back(A_thread[i][0]);    // 每个线程产生的A事件数量都为1， 这里可以优化(其实用不着每个线程的结果都存储在单独的vector中？或许为了数据不冲突不加锁，这是必要的)  >>>

            int valid_p_A = 0;    // temp_A_list中有效的（不需要回退的）A事件 的 下标为0到valid_p_A

            for(size_t i = 1; i < temp_A_list.size(); i++)    //以下对temp_A_list中的A事件进行是否跨越阈值产生D事件的判断
            {
                if((last_D != NULL) && (compare_double(last_D->T + last_D->dT, temp_A_list[i]->T) < 0))
                       //如果最新的D事件的T + dT 小于 当前循环的A事件的T，那么接下来（包括当前A事件）都需要回退，可令循环终止
                    break;                         

                valid_p_A = i;

                double former_V = temp_A_list[i-1]->V;
                double current_V = temp_A_list[i]->V;
                if(((compare_double(former_V, Vth) <= 0) && (compare_double(current_V, Vth) > 0)) || ((compare_double(former_V, Vth) > 0) && (compare_double(current_V, Vth) <= 0)))   //如果跨过了阈值电压，则要产生事件D
                {
                    temp_A_list[i]->bM = 1;
                    Event* tmp_D;
                    Sim_D->Syn(temp_A_list[i], tmp_D);       //D进行同步
                    Sim_D->list_D.push_back(static_cast<Devent*>(tmp_D));
                    last_D = tmp_D;   // 更新last_D
                } 

                if(last_D != NULL)
                {
                    if((temp_A_list[i]->bM != 1) && (compare_double(last_D->T + last_D->dT, temp_A_list[i]->T) == 0))  
                    {      // 如果这个A事件的bM!=1 且 如果上一个D事件的T + dT 等于 当前循环的A事件的T，那么这个A事件要被覆盖(即回退)
                        valid_p_A-=1;    // valid_p_A减一 表示对当前的A事件进行回退
                        break;
                    }
                }
            }

            for(int i = first ? 0 : 1; i <= valid_p_A; i++) // 存储有效的A事件（first == false时即非第一次任务时，temp_A_list的第0个元素都是上次已经存储的结果，所以不用存储）
                Sim_A->list_A.push_back(temp_A_list[i]);
            for(size_t i = valid_p_A+1; i < temp_A_list.size(); i++)  // delete回退的A事件
            {
                delete temp_A_list[i];
                temp_A_list[i] = NULL;
            }

            if(first == true)    //第一轮之后就令first=false
                first = false;
            
            Aevent* last_A = Sim_A->list_A.back();
            if(last_D != NULL)  //确定D_event_num,如果last_D == NULL,那么D_event_num就等于0，实际上这个不用写，只要last_D == NULL一定有D_event_num就等于0
            {
                double d1 = last_D->T + last_D->dT;
                double d2 = last_A->T;
                double d3 = last_A->T + last_A->dT;
                if((compare_double(d1,d2) > 0) && (compare_double(d1,d3) < 0))
                    D_event_num = 1;
                else if(compare_double(d1,d3) > 0)
                    D_event_num = 0;
                else if(compare_double(d1,d3) ==0)
                {
                    if(valid_p_A == (temp_A_list.size()-1))    // 如果没有A事件被回退
                        D_event_num = 0;
                    else
                        D_event_num = 1;
                }
            }

            if((compare_double(last_A->T + last_A->dT, Tsim) >= 0)   &&
                ( (last_D == NULL) || ((last_D != NULL) && (compare_double(last_D->T + last_D->dT, Tsim) >= 0))))
                loop = false;         // 如果最新一个A事件的T+dT > Tsim 且 最新一个D事件的 T+dT > Tsim, 则仿真结束
            if((D_event_num == 1) && (compare_double((Tsim - (last_D->T + last_D->dT)),dT) < 0))   //另一种仿真结束的情况：num_event_D = 1的情况下，
                                            // 如果Tsim - (last_D->T + last_D->dT) < dT，那么马上推进一个D事件，且马上同步产生A事件，且存储A和D，仿真结束
            {
                Sim_D->Push_Sim(0.0, last_D);
                Sim_D->list_D.push_back(static_cast<Devent*>(last_D));  //对last_D进行存储
                Event* tmp_A;
                Sim_A->Syn(last_D, tmp_A);     //A进行同步
                Sim_A->list_A.push_back(static_cast<Aevent*>(tmp_A));
                loop = false;
            }
        }        

        print_info(true);   // 这次对于事件的打印没有分成多次进行，之后需要的话可以修改 >>>
        print_end();
    }


    void set_tA(int ta)
    {
        Sim_A->set_tA(ta);
    }
};

int main()
{
    // 起始时间           -------------测定程序运行时间
    //auto start = std::chrono::high_resolution_clock::now();

    srand(time(0));
    string filename = "con.txt";
	string outfile_name = "sim_res.txt";

	double dt, vth, tsim;
    int ta;
    //读取参数，检验是否有报错
    try {
        readFile(dt,vth,tsim,ta,filename);
    } catch (const std::exception& e) {
        std::cerr << "错误信息: " << e.what() << std::endl;
        return 1;
    }
    ofstream outfile(outfile_name);  //打开输出文件
    if (!outfile.is_open()) {
        std::cerr << "无法打开文件！" << std::endl;
        return 1;
    }
    get_num_thread(dt,vth,tsim); //更新最大线程
    //std:;cout<< max_num_thread << endl;
    // 使用streambuf来重定向cout到文件
    std::streambuf *backup = std::cout.rdbuf(); // 备份原始的cout缓冲区
    std::cout.rdbuf(outfile.rdbuf());        // 将cout的缓冲区设置为文件的缓冲区

    Sim_plate* Sp1;
    Sp1 = new Sim_plate(dt, vth, tsim);

    Sp1->set_tA(ta);       //设置tA

    R_V = 0.001;
    R_T = 0.001;    
    Sp1->start_Sim();

    // 重置cout的缓冲区到原始的缓冲区，这样cout的输出就会回到标准输出
    std::cout.rdbuf(backup);
    outfile.close();

    //printf("over!\n");
    // 结束时间        -------------测定程序运行时间
    //auto end = std::chrono::high_resolution_clock::now();

    // 计算耗时（单位：毫秒）
    //std::chrono::duration<double, std::milli> duration = end - start;

    //std::cout << "runtime: " << duration.count() << " ms" << std::endl;
    return 0;
}
